using System;
using System.Collections;

namespace NUnit.Behave
{
    public abstract class Context
    {
        protected ArrayList addVerbCatalog = new ArrayList();
        protected Hashtable delegateActionCatalog = new Hashtable();
        protected MessageType singleMessageType;
        protected MessageType conjunctionMessageType;
        protected MessageType singleMessageTypePending;
        protected MessageType conjunctionMessageTypePending;

        protected static void AddMessageToMessageProvider(IMessageProvider provider, AddVerb addVerb,
                                                          MessageType messageType)
        {
            provider.AddMessage(messageType, addVerb.ToString());
        }

        protected static void EvaluateNestedStateAndWriteToProvider(AddVerb addVerb, bool isNested,
                                                                    IMessageProvider messageProvider,
                                                                    MessageType addVerbMessageType,
                                                                    MessageType addVerbConjunctionMessageType)
        {
            if (isNested)
            {
                AddMessageToMessageProvider(messageProvider, addVerb, addVerbConjunctionMessageType);
            }
            else
            {
                AddMessageToMessageProvider(messageProvider, addVerb, addVerbMessageType);
            }
        }

        protected void InterateThroughCatalogAndWriteToProvider(IMessageProvider provider)
        {
            bool isNested = false;
            foreach (AddVerb addVerb in addVerbCatalog)
            {
                EvaluateNestedStateAndWriteToProvider(addVerb, isNested, provider, singleMessageType,
                                                      conjunctionMessageType);

                isNested = true;
            }
        }

        protected abstract void PassToNextProvider(IMessageProvider provider);

        public void RenderTo(IMessageProvider provider)
        {
            InterateThroughCatalogAndWriteToProvider(provider);
            PassToNextProvider(provider);
        }

        protected static void EnvokeActionDelegate<T>(T value, Action<T> delegateAction)
        {
            delegateAction(value);
        }

        protected void StoreActionAndEnvokeDelegate<T>(T actionValue, Action<T> delegateAction, string message)
        {
            if (!IsMessageAlreadyRegisteredCatalog(message))
                delegateActionCatalog.Add(message, delegateAction);

            EnvokeActionDelegate(actionValue, delegateAction);
        }

        protected static void WriteToConsole(string verbMessage, MessageType messageType)
        {
            Console.WriteLine(ConsoleFormatter.FormatMessage(verbMessage, messageType));
        }

        protected bool IsMessageAlreadyRegisteredCatalog(string givenMessage)
        {
            return delegateActionCatalog.ContainsKey(givenMessage);
        }

        protected TOutput EvaluateMessageForCatalogEntry<TInput, TOutput>(TOutput contextType, string givenMessage,
                                                                          TInput actionValue, MessageType messageType)
        {
            if (IsMessageAlreadyRegisteredCatalog(givenMessage))
            {
                return And(contextType, givenMessage, actionValue, (Action<TInput>) delegateActionCatalog[givenMessage]);
            }
            else
            {
                return WriteToConsoleAndAddToCatalog(contextType, actionValue, givenMessage, messageType);
            }
        }

        public TOutput And<TInput, TOutput>(TOutput contextType, string givenMessage, TInput actionValue,
                                            Action<TInput> delegateAction)
        {
            return
                EnvokeActionAndStoreForLaterUse(contextType, actionValue, delegateAction, givenMessage,
                                                conjunctionMessageType);
        }

        protected TOutput EnvokeActionAndStoreForLaterUse<TInput, TOutput>(TOutput contextType, TInput actionValue,
                                                                           Action<TInput> delegateAction,
                                                                           string givenMessage, MessageType messageType)
        {
            StoreActionAndEnvokeDelegate(actionValue, delegateAction, givenMessage);

            WriteToConsole(AddVerb.FormatToString(givenMessage, actionValue), messageType);

            return contextType;
        }

        private TOutput WriteToConsoleAndAddToCatalog<TInput, TOutput>(TOutput contextType, TInput actionValue,
                                                                       string givenMessage, MessageType messageType)
        {
            WriteToConsole(AddVerb.FormatToString(givenMessage, actionValue), messageType);

            return AddTheAddVerbToTheCatalog(contextType, givenMessage, actionValue);
        }

        private TOutput AddTheAddVerbToTheCatalog<TInput, TOutput>(TOutput contextType, string thenMessage,
                                                                   TInput actionValue)
        {
            AddVerb addVerb = new AddVerb(thenMessage);
            addVerb.AddActionValue(actionValue);
            addVerbCatalog.Add(addVerb);
            return contextType;
        }
    }
}